import parole, pygame

import random
import interface, sim, town, dungeon
from util import *
shader = parole.shader

#############################################################################

titleScreenAction = None

schedule = None
mapFrame = None
cmdPoll = None
player = None
statusBox = None

# annotations
examineAnnote = None
transientAnnotes = []  # Annotations that are only displayed until the next
                       # input

waitTime = 1000 # waiting is for 1 second

levels = {}
currentLevel = None

#############################################################################

introText = \
"""
\t\t\t\t\t\t\t\t\t\t{\Gold Introduction}
The town of Anoiktos has enjoyed a tranquil and bucolic existence since its founding. Nomadic herders from the east making their way through the Arcadian pass stumbled upon an emerald expanse of verdant fields 200 years ago. Stunned by the beauty of the valley, the herders decided to settle down, and over the years Anoiktos has evolved from a settlement to a hamlet to a vibrant trading town.

The men from the east were not the first to dwell in that valley in the shadow of the Mavros mountains. It was common knowledge throughout the region that a bustling city resided there long ago. Its citizens enjoyed a secluded life and rarely traded with even their closest neighbors. The city was destroyed in a mysterious cataclysm centuries ago, but due to its isolation it was years before outsiders were even aware of the city's obliterartion. Now a few scattered obsidian monoliths dotting the valley are the only visible remains of this forgotten civilization.

As Anoiktos' prosperity grew, it was decided that the old mine near the village, left over from the previous inhabitants, would be restored. For three years the mine yielded bronze, silver, and valuable gems that greatly enriched the town. Three years to the day after the mine was restored, a freak accident killed most of the miners. The traumatized survivors fled to the surface and quickly sealed the mine, refusing to provide further details to the rest of the townsfolk. That is when the troubles began.

The people of Anoiktos have not had a restful night since that day. Noises coming from deep within the mine make sleep almost impossible, and when it does come it brings dreams filled with an incessant, dark whispering. Multiple townspeople report sleepwalking, only to wake up in front of the mine with bloody fingers futilely gripping the chondrite padlock. The mayor of the town has issued a decree throughout the region, offering a handsome reward to anyone willing to enter the mine and destroy whatever lurks below. Eager for riches and fame, you have heeded the call...

You arrive with the onset of night. The mayor gives you a key to open the solid chondrite hatch barring the entrance to mines, and promptly shuts himself in his house.
"""[1:-1]

#############################################################################

deathText = \
"""
\t\t\t\t\t\t\t\t\t{\Gold Death Comes}
You fall to your knees, clutching a deep gash in your neck in a vain attempt to stop the blood loss. Seeing an opportunity, your foe lunges forward and delivers a killing blow. The world flashes a bright white for a moment before everything quickly fades to gray. Time passes. It may be seconds or it may be centuries. There is no way to tell. At first you retain a dim sense of self but it too begins to fade. Soon most of your persona dissolves into nothingness. You are a shadow of your former self. All that remains is an awareness of your failure and a burning hunger.
"""

#############################################################################

victoryText = \
"""
\t\t\t\t\t\t\t\t\t\t{\Gold Victory!}
Your eyes are drawn to a pedestal of surrounded by towering obsidian pillars.  On this pedestal rests a metal box of asymmetrical form; its interior holding a nearly black, red-striated polyhedron with many irregular flat surfaces.  Examining the object at great length, you find that you cannot tear your eyes away.  You stand there staring for what feels like hours.  Visions of a procession of hooded, inhuman figures in the shadow of a cyclopean, ebony monolith flood your mind, only to recede into an infinite gulf of darkness.  Mustering all your willpower you avert your gaze and break the reverie, stumbling backwards.  You raise your weapon and smash the accursed object.  A feeling of peace washes over you.
"""

#############################################################################

def transient(text, posOrTile=None):
    t = None
    if posOrTile:
        if type(posOrTile) is tuple:
            t = mapFrame.getMap()[posOrTile]
        else:
            assert(type(posOrTile) is parole.map.Tile)
            t = posOrTile
    else:
        t = player.parentTile
    transientAnnotes.append(mapFrame.annotate(t, text))

#############################################################################

def doFrame():
    # Fonts that get used everywhere
    global proggySmall, arial, player, schedule
    proggySmall = parole.resource.getFont('fonts/ProggySmall.ttf', 16)
    arial = parole.resource.getFont('fonts/Arial.ttf', 12)

    interface.init()

    global titleScreenAction
    while 1:
        player = None
        schedule = None
        titleScreenAction = None
        # Start up with title screen
        parole.display.scene.removeAll()
        parole.display.clearSurface(parole.display.getSurface())
        #drawStarfield()
        showTitleScreen()
    
        # Start playing some sweet music
        titleMusic = \
            parole.resource.getSound(parole.conf.interface.titleScreen.music)
        if titleMusic and parole.conf.interface.music.enabled:
            titleMusic.set_volume(parole.conf.interface.music.volume)
            titleMusic.play(-1)

        # DEBUG: proceed directly to map
        #titleScreenAction = 'newGame'
        titleScreenAction = None
    
        # Wait till the user makes a choice
        while not titleScreenAction:
            yield
    
        # Handle the choice
        if titleScreenAction in ("quit", "newGame"):
            t = parole.time()
            if titleMusic and parole.conf.interface.music.enabled:
                titleMusic.fadeout(1000)
            for a in interface.fadeOut(1000):
                yield

        if titleScreenAction == "quit":
            raise parole.ParoleShutdown
        elif titleScreenAction == "newGame":
            weAreCurrently = {'waiting':True}

            # character creation
            rlBox = parole.shader.ReadLineBox(arial, 300, 
                prompt='Enter your name: ')
            frame = parole.shader.Frame(interface.frameBorders, 
                    contents=[parole.shader.ColorField((128,0,0,200),
                        (rlBox.width+10, rlBox.height)), rlBox])
            frame.update()
            def onEnterName(readline):
                weAreCurrently['waiting'] = False
                weAreCurrently['named'] = readline.text.strip()
                parole.display.scene.remove(frame)

            rl = parole.input.ReadLine(onEnterName, rlBox.onInput)
            parole.display.scene.add(frame, pos=frame.centeredPos())
            parole.pushUIEventHandler(rl)
            while weAreCurrently['waiting']:
                yield

            #parole.display.update()
            parole.info('Player name: %s', weAreCurrently['named'])

            statUpper = interface.StatUpper(6)
            statUpper.push()
            while statUpper.active:
                yield

            # A make a da playa
            schedule = sim.Schedule()
            player = sim.Player(weAreCurrently['named'],
                statUpper.str,
                statUpper.dex,
                statUpper.con,
                statUpper.per
            )

            # Let's do this thing!
            thetown, ustairPos, dstairPos = town.makeTown()
            addLevel('town', thetown, ustairPos, dstairPos)

            for frame in runSim(schedule, thetown, ustairPos):
                if player and player.dead:
                    break
                elif player and player.winner:
                    break
                yield

            # player died
            if player.dead:
                clearTransientAnnotations()
                mapFrame.setMap(None)
                weAreCurrently = {'waiting':True}
                def onClose():
                    weAreCurrently['waiting'] = False
                interface.ModalBox(deathText, arial, onClose).push()
                while weAreCurrently['waiting']:
                    yield

            # player won
            elif player.winner:
                clearTransientAnnotations()
                mapFrame.setMap(None)
                weAreCurrently = {'waiting':True}
                def onClose():
                    weAreCurrently['waiting'] = False
                interface.ModalBox(victoryText, arial, onClose).push()
                while weAreCurrently['waiting']:
                    yield
            

#############################################################################

def showTitleScreen():
    # The main title bill (read from text/title.txt) centered high
    #parole.info(parole.display.screenTextSize(proggySmall))
    titleText = parole.resource.getResource('text/title.txt')
    tbTitle = parole.shader.TextBlockPass(proggySmall, (128,128,128), (0,0,0,0),
            text=titleText)
    tbTitle.update()
    titleRect = pygame.Rect((0,0), tbTitle.size)
    scrRect = parole.display.getSurface().get_rect()
    titleRect.centerx = scrRect.centerx
    titleRect.top = 200
    parole.display.scene.add(tbTitle, pos=titleRect.topleft)

    # Subtitle five pixels under the main
    tbSubTitle = parole.shader.TextBlockPass(arial, (255,255,255), 
            text="Halls of Darkness")
    tbSubTitle.update()
    subRect = pygame.Rect((0,0), tbSubTitle.size)
    subRect.center = titleRect.center
    subRect.top = titleRect.bottom + 5
    parole.display.scene.add(tbSubTitle, pos=subRect.topleft)

    # Horziontal lines flanking the subtitle
    lineL = parole.shader.Line((titleRect.left, subRect.centery),
                               (subRect.left, subRect.centery),
                               parole.colornames.colors['Red'])
    parole.display.scene.add(lineL, pos=lineL.defaultPos)
    lineR = parole.shader.Line((subRect.right+1, subRect.centery),
                               (titleRect.right, subRect.centery),
                               parole.colornames.colors['Red'])
    parole.display.scene.add(lineR, pos=lineR.defaultPos)

    # LineChooser in a frame
    # Choices: Start Test, Quit
    chFrame = interface.LineChooserFrame(proggySmall, 
            [('New Game', newGame),
             ('Quit', quit)])
    chFrame.update()
    frameRect = pygame.Rect((0,0), chFrame.size)
    frameRect.centerx = titleRect.centerx
    frameRect.top = subRect.bottom + 20
    parole.display.scene.add(chFrame, pos=frameRect.topleft)

    parole.pushUIEventHandler(chFrame.chooser.cmdMap)

    # Copyright info at bottom
    tbCopy = shader.TextBlockPass(arial, (180,180,180), (0,0,0),
        text="Copyright 2009 by Mary Haas and James Madison. You have no rights.")
    tbCopy.update()
    copyRect = pygame.Rect((0,0), tbCopy.size)
    copyRect.bottom = scrRect.bottom-2
    copyRect.centerx = scrRect.centerx
    parole.display.scene.add(tbCopy, pos=copyRect.topleft)

#############################################################################

def quit(line):
    global titleScreenAction
    titleScreenAction = "quit"

#############################################################################

def newGame(line):
    global titleScreenAction
    titleScreenAction = "newGame"

#############################################################################

def setCurrentMap(map, startPos):
    global currentLevel

    if player.parentTile:
        player.listen(sim.Event('leave tile', schedule.time, (player,
            player.pos), dests=[player]))

    clearTransientAnnotations()
    if mapFrame.getMap():
        mapFrame.setMap(None)

    mapFrame.setMap(map)
    mapFrame.centerAtTile(startPos)

    map[startPos].add(player)
    player.listen(sim.Event('enter tile', schedule.time+1, (player, startPos, map),
        dests=[player]))
    
    for level, info in levels.iteritems():
        if info[0] is map:
            currentLevel = level

#############################################################################

def addLevel(level, map, ustairPos, dstairPos):
    levels[level] = (map, ustairPos, dstairPos)

#############################################################################

def runSim(sched, curMap, startPos):
    global player, schedule, mapFrame, cmdPoll, examineAnnote, statusBox, \
           currentLevel
    schedule = sched
    examineAnnote = None

    if not player:
        parole.warn('runSim: null player, creating default one.')
        player = sim.Player()

    if not curMap:
        #curMap, ustairPos, dstairPos = town.makeTown()
        curMap, ustairPos, dstairPos = dungeon.makeLevel(1)
        addLevel(1, curMap, ustairPos, dstairPos)
        startPos = ustairPos

    # set up the status display
    statusBox = interface.StatusBox(proggySmall, player)
    statusBox.update()

    # Set up a map frame 
    scrSize = parole.display.getSurface().get_size()
    mapFrame = parole.map.MapFrame((scrSize[0], scrSize[1]-statusBox.height))

    # display them
    parole.display.scene.add(mapFrame, pos=(0, statusBox.height))
    parole.display.scene.add(statusBox, pos=(statusBox.centeredPos()[0], 0))

    # be sure to unlisten before saving!
    schedule.listen('set stat', statusBox) 
    schedule.listen('increase stat', statusBox) 
    schedule.listen('decrease stat', statusBox) 
    schedule.listen('enter inventory', statusBox)
    schedule.listen('leave inventory', statusBox)

    # Set up a ui event handler for player commands
    cmap = parole.input.CommandMap(parole.conf.interface.mainplay, 
            handleCommand)
    parole.pushUIEventHandler(cmap)

    cmdPoll = CommandPoll()
    schedule.listen('get command', cmdPoll)

    # set the map and add the player to the map
    setCurrentMap(curMap, startPos)

    # start listening for commands at the next game-millisecond
    schedule.add(sim.Event('get command', 1))

    
    # show intro
    weAreCurrently = {'waiting':True}
    def onClose():
        weAreCurrently['waiting'] = False
    interface.ModalBox(introText, arial, onClose).push()

    while 1:
        if transientAnnotes and mapFrame.selectedTile:
            mapFrame.selectTile(None)

        # check for leveling
        if (player.level + 1) in player.levelXP:
            if player['xp'] >= player.levelXP[player.level+1]:
                statUpper = interface.StatUpper(4)
                statUpper.push()
                while statUpper.active:
                    yield

                player.level += 1
                schedule.add(sim.Event('set stat', schedule.time, (player,
                    'strength', statUpper.str), dests=[player]))
                schedule.add(sim.Event('set stat', schedule.time, (player,
                    'dexterity', statUpper.dex), dests=[player]))
                schedule.add(sim.Event('set stat', schedule.time, (player,
                    'constitution', statUpper.con), dests=[player]))
                schedule.add(sim.Event('set stat', schedule.time, (player,
                    'perception', statUpper.per), dests=[player]))

        while not cmdPoll.awaitCommand:
            parole.debug('ADVANCING SCHEDULE...')
            schedule.advance()

        yield

def clearTransientAnnotations():
    # Clear transient annotations
    for ann in transientAnnotes:
        if ann:
            mapFrame.removeAnnotation(ann)
    del transientAnnotes[:]
    if player and player.parentTile and not examineAnnote:
        mapFrame.selectTile(player.pos)

class MapSender(sim.Listener):
    def __init__(self, nm, sp):
        self.nm, self.sp = nm, sp
    def listen(self, event):
        if event.id == 'get command':
            setCurrentMap(self.nm, self.sp)
            schedule.unlisten('get command', self)

def handleCommand(cmd):
    global examineAnnote 

    if not cmdPoll.awaitCommand:
        parole.error('*** Got a command while not waiting for one!')
        return

    clearTransientAnnotations()
    map = mapFrame.getMap()

    #parole.info('cmd: %s', cmd)
    if cmd == "view left":
        mapFrame.scrollTiles(-1,0)
        return
    elif cmd == "view right":
        mapFrame.scrollTiles(1,0)
        return
    elif cmd == "view down":
        mapFrame.scrollTiles(0,1)
        return
    elif cmd == "view up":
        mapFrame.scrollTiles(0,-1)
        return
    elif cmd == "quit":
        # TODO: proper quitting
        raise parole.ParoleShutdown
    elif cmd == "examine":
        # Toggle examination
        if not examineAnnote:
            examineAnnote = mapFrame.annotate(player.parentTile,
                    'You see: %s.' % ', '.join([x.noun() for x in \
                        player.visibleObjectsAt(player.parentTile)]),
                    )
            mapFrame.selectTile(None)
            if player.monsterReporting:
                reportMonster(player.parentTile)
        else:
            mapFrame.removeAnnotation(examineAnnote)
            examineAnnote = None
            mapFrame.selectTile(player.pos)
        return
    elif cmd == "clear":
        # Untoggle any togglable thing that's on.
        # Includes: examine
        if examineAnnote:
            mapFrame.removeAnnotation(examineAnnote)
            examineAnnote = None
        return

    elif cmd.startswith('walk '):
        # either move the player, or move the cursor if we're examining
        if not examineAnnote:
            schedule.add(sim.Event('walk', schedule.time,
                args=cmd.split()[1:], dests=[player]))
        else:
            dir = cmd.split()[1:][0]
            dx, dy = displacement(dir)
            newPos = examineAnnote.tile.col + dx, examineAnnote.tile.row + dy
            if newPos[0] < 0 or newPos[0] >= examineAnnote.tile.map.cols \
                or newPos[1] < 0 or newPos[1] >= examineAnnote.tile.map.rows:
                    return
            newTile = examineAnnote.tile.map[newPos]
            mapFrame.removeAnnotation(examineAnnote)
            if (newTile.col, newTile.row) in mapFrame.visibleTiles \
                    or (not mapFrame.fovObj):
                examineAnnote = mapFrame.annotate(newTile,
                        'You see: %s.' % ', '.join([x.noun() for x in \
                            player.visibleObjectsAt(newTile)]))
            else:
                examineAnnote = mapFrame.annotate(newTile,
                        'You cannot see there.')
                
            if player.monsterReporting:
                reportMonster(newTile)
            return
    elif (cmd == "more ambient") and parole.conf.options.wizard:
        # debug command
        map.setAmbientLight((255,255,255), min(1., map.ambientIntensity + 0.05))
        map.update()
        return
    elif (cmd == "less ambient") and parole.conf.options.wizard:
        # debug command
        map.setAmbientLight((255,255,255), max(0., map.ambientIntensity - 0.05))
        map.update()
        return
    elif (cmd == "toggle fov") and parole.conf.options.wizard:
        # debug command
        if not mapFrame.fovObj:
            mapFrame.bindVisibilityToFOV(player, player['perception'],
                remember=False, fovCondition=player.tileIsBrightEnough)
        else:
            mapFrame.bindVisibilityToFOV(None, 0)
        map.update()
        return
    elif cmd in ('open', 'close'):
        # toggle adjacent door, or ask if multiple

        def __tryToggleDoor(thedoor):
            # once the intended door is known, this tries opening it and
            # scheduling the next get-command
            evtime = player.toggleDoor(thedoor)
            if evtime < 0:
                # nothing happened; get another command
                schedule.add(sim.Event('get command', schedule.time))
            else:
                schedule.add(sim.Event('get command', evtime+1))

        def __handleDoorDir(disp):
            clearTransientAnnotations()
            dx, dy = disp
            doorPos = player.parentTile.col + dx, player.parentTile.row + dy
            if doorPos[0] < 0 or doorPos[0] >= map.cols \
                or doorPos[1] < 0 or doorPos[1] >= map.rows:
                    return
            doorTile = map[doorPos]
            for obj in doorTile:
                if isinstance(obj, sim.Door):
                    __tryToggleDoor(obj)
                    cmdPoll.awaitCommand = False
                    return
            transientAnnotes.append(mapFrame.annotate(doorTile,
                "There is no door there."))

        def __handleDoorDirCancel():
            clearTransientAnnotations()

        door = None
        for t in adjacentTiles(player.parentTile):
            for obj in t:
                if isinstance(obj, sim.Door):
                    if not door:
                        door = obj
                    else:
                        # there are multiple doors
                        transientAnnotes.append(mapFrame.annotate(player.parentTile,
                            "Which direction?"))
                        interface.DirectionChooser(__handleDoorDir,
                                __handleDoorDirCancel).push()
                        return
                        
        if door:
            __tryToggleDoor(door)
        else:
            transientAnnotes.append(mapFrame.annotate(player.parentTile,
                "There is no door nearby."))
            return

    elif cmd == 'get':
        def __tryPickup(item):
            evtime = player.pickupItem(item)
            if evtime < 0:
                # nothing happened; get another command
                schedule.add(sim.Event('get command', schedule.time))
            else:
                schedule.add(sim.Event('get command', evtime + 1))

        def __handleChooseGet(item):
            cmdPoll.awaitCommand = False
            __tryPickup(item)
            return

        # find an item in the player's tile
        target = None
        for obj in player.parentTile:
            if isinstance(obj, sim.Item):
                if target:
                    # there are multiple possible items
                    interface.ObjectChooser(
                            [o for o in player.parentTile if isinstance(o,
                                sim.Item)],
                            __handleChooseGet,
                            title="{\Gold Pick up which item?}").push()
                    return
                else:
                    target = obj
        if target:
            __tryPickup(target)
        else:
            transientAnnotes.append(mapFrame.annotate(player.parentTile,
                "There is nothing here to pick up."))
            return
        
    elif cmd == 'drop':
        def __doDrop(target):
            evtime = player.dropItem(target)
            if evtime < 0:
                # nothing happened; get another command
                schedule.add(sim.Event('get command', schedule.time))
            else:
                schedule.add(sim.Event('get command', evtime + 1))
            return

        def __handleChooseDrop(target):
            cmdPoll.awaitCommand = False
            __doDrop(target)
            return

        if not player.inventory:
            transientAnnotes.append(mapFrame.annotate(player.parentTile,
                "You have nothing to drop."))
            return
        if len(player.inventory) == 1:
            target = player.inventory[0]
            __doDrop(target)
        else:
            # choose what to drop
            interface.ObjectChooser(player.inventory, __handleChooseDrop,
                    title="{\Gold Drop which item?}").push()
            return

    elif (cmd == "toggle clipping") and parole.conf.options.wizard:
        player.clipping = not player.clipping
        return

    elif cmd == "inventory":
        if not player.inventory:
            transient("You are unburdened by the sin of possession.")
            return

        interface.ObjectChooser(player.inventory, lambda target: 0, 
            title="{\Gold You carry the following, for a total weight of %s lbs "
            "(capacity %s lbs):}" % (player.inventoryWeight(),
                player.inventoryCapacity())).push()
        return

    elif cmd == "wear":
        targets = []
        for obj in player.inventory:
            if isinstance(obj, sim.Wearable) and obj not in \
                    player.wearSlots.values():
                targets.append(obj)

        if not targets:
            transient("You have nothing that could be worn or wielded.")
            return

        def __handleChooseWearable(target):
            cmdPoll.awaitCommand = False
            evtime = player.wear(target)
            if evtime < 0:
                # nothing happened; get another command
                schedule.add(sim.Event('get command', schedule.time))
            else:
                schedule.add(sim.Event('get command', evtime + 1))
            

        interface.ObjectChooser(targets, __handleChooseWearable, 
            title="Wear or wield what?").push()
        return

    elif cmd == "remove":
        targets = []
        for obj in player.wearSlots.values():
            if obj:
                targets.append(obj)

        if not targets:
            transient("You've nothing to remove or unwield.")
            return

        def __handleChooseWearable(target):
            cmdPoll.awaitCommand = False
            evtime = player.remove(target)
            if evtime < 0:
                # nothing happened; get another command
                schedule.add(sim.Event('get command', schedule.time))
            else:
                schedule.add(sim.Event('get command', evtime + 1))

        interface.ObjectChooser(targets, __handleChooseWearable, 
            title="Remove or unwield what?").push()
        return

    elif cmd == "equipment":
        equip = []
        for obj in player.wearSlots.values():
            if obj:
                equip.append(obj)
        if not equip:
            transient("You've nothing worn or wielded.")
            return

        interface.ObjectChooser(equip, lambda target: 0, 
            title="{\Gold You wear and wield the following:}" ).push()
        return

    elif (cmd == "toggle monster reporting") and parole.conf.options.wizard:
        player.monsterReporting = not player.monsterReporting
        return

    elif (cmd == "wait"):
        schedule.add(sim.Event('get command', schedule.time+1000))

    elif (cmd == "light"):
        lights = []
        existingSource = None
        for obj in player.inventory:
            if isinstance(obj, sim.LightSource):
                if obj.on:
                    existingSource = obj
                else:
                    # we can light carried lightsources 
                    lights.append(obj)
        for obj in player.parentTile:
            if isinstance(obj, sim.LightSource) and not obj.on:
                # we can light lightsources on the ground
                lights.append(obj)

        if not lights:
            transient("There is nothing nearby to light.")
            return

        def __handleChooseLight(target):
            cmdPoll.awaitCommand = False
            evtime = player.light(target)
            if evtime < 0:
                # nothing happened; get another command
                schedule.add(sim.Event('get command', schedule.time))
            else:
                schedule.add(sim.Event('get command', evtime + 1))
            

        interface.ObjectChooser(lights, __handleChooseLight, 
                title="{\Gold Choose an item to light with your %s:}" %\
                        (existingSource and existingSource.noun(proper=1) \
                         or 'flint',)).push()
        return

    elif cmd == 'snuff':
        lights = []
        existingSource = None
        for obj in player.inventory:
            if isinstance(obj, sim.LightSource):
                if obj.on:
                    lights.append(obj)
        for obj in player.parentTile:
            if isinstance(obj, sim.LightSource) and obj.on:
                # we can light lightsources on the ground
                lights.append(obj)

        if not lights:
            transient("There is no lightsource nearby to snuff.")
            return

        def __handleChooseSnuff(target):
            cmdPoll.awaitCommand = False
            evtime = player.snuff(target)
            if evtime < 0:
                # nothing happened; get another command
                schedule.add(sim.Event('get command', schedule.time))
            else:
                schedule.add(sim.Event('get command', evtime + 1))
            

        interface.ObjectChooser(lights, __handleChooseSnuff, 
                title="{\Gold Choose a light to snuff:}").push()
        return

    elif cmd.startswith('makelevel '):
        depth = int(cmd[-1])
        setCurrentMap(*(dungeon.makeLevel(depth)[:2]))
        schedule.add(sim.Event('get command', schedule.time+2))

    elif cmd == 'descend':
        thisLevel = currentLevel
        thisMap, thisStartPos, thisDownPos = levels[currentLevel]
        if player.pos != thisDownPos:
            transient('There is no way down here.')
            return

        if currentLevel == 'town':
            nextLevel = 1
        else:
            nextLevel = currentLevel + 1
        if nextLevel not in levels:
            thelevel, ustairPos, dstairPos = dungeon.makeLevel(nextLevel)
            addLevel(nextLevel, thelevel, ustairPos, dstairPos)
        nextMap, startPos, dstairPos = levels[nextLevel]

        schedule.listen('get command', MapSender(nextMap, startPos))
        schedule.add(sim.Event('get command', schedule.time + \
                (thisLevel != 'town' and 30000 or 2000)))

    elif cmd == 'ascend':
        thisLevel = currentLevel
        thisMap, thisUpPos, thisDownPos = levels[currentLevel]
        if player.pos != thisUpPos:
            transient('There is no way up here.')
            return

        if currentLevel == 'town':
            transient('There is no way up here.')
            return
        elif currentLevel == 1:
            nextLevel = 'town'
        else:
            nextLevel = currentLevel - 1

        if nextLevel not in levels:
            thelevel, ustairPos, dstairPos = dungeon.makeLevel(nextLevel)
            addLevel(nextLevel, thelevel, ustairPos, dstairPos)

        if nextLevel == 'town':
            nextMap, xxx, startPos = levels[nextLevel]
        else:
            nextMap, startPos, dstairPos = levels[nextLevel]

        schedule.listen('get command', MapSender(nextMap, startPos))
        schedule.add(sim.Event('get command', schedule.time + \
                (thisLevel != 'town' and 30000 or 2000)))

    elif cmd == 'quaff':
        targets = []
        for obj in player.inventory:
            if isinstance(obj, sim.Potion):
                targets.append(obj)

        if not targets:
            transient("You have no potions.")
            return

        def __handleChoosePotion(target):
            cmdPoll.awaitCommand = False
            evtime = player.quaff(target)
            if evtime < 0:
                # nothing happened; get another command
                schedule.add(sim.Event('get command', schedule.time))
            else:
                schedule.add(sim.Event('get command', evtime + 1))
            

        interface.ObjectChooser(targets, __handleChoosePotion, 
            title="{\Gold Quaff which potion?}").push()
        return

    elif cmd == 'read':
        targets = []
        for obj in player.inventory:
            if isinstance(obj, sim.Scroll):
                targets.append(obj)

        if not targets:
            transient("You have no scrolls.")
            return

        def __handleChooseScroll(target):
            cmdPoll.awaitCommand = False
            evtime = player.read(target)
            if evtime < 0:
                # nothing happened; get another command
                schedule.add(sim.Event('get command', schedule.time))
            else:
                schedule.add(sim.Event('get command', evtime + 1))
            

        interface.ObjectChooser(targets, __handleChooseScroll, 
            title="{\Gold Read which scroll?}").push()
        return

    else:
        parole.warn('Unhandled command: %r', cmd)
        return

    cmdPoll.awaitCommand = False

#========================================

class CommandPoll(sim.Listener):
    awaitCommand = False

    def listen(self, event):
        if event.id == 'get command':
            self.awaitCommand = True
            parole.debug('\n\n\nAwaiting command [time=%d]...', schedule.time)

#========================================

def reportMonster(tile):
    for obj in tile:
        if isinstance(obj, sim.Creature):
            parole.info('Reporting on: %r', obj)
            for statname in obj.statNames:
                if 'Mod' not in statname:
                    total = obj[statname]
                    base = getattr(obj, statname)
                    try:
                        mod = getattr(obj, statname+'Mod')
                    except AttributeError:
                        mod = 'n/a'
                    parole.info('\t%s = %s = %s + %s', statname, total, base, mod)
